package com.zjj.springstu.springHandwriting.framework.context;

import com.zjj.springstu.springHandwriting.framework.annotation.Component;
import com.zjj.springstu.springHandwriting.framework.annotation.ComponentScan;
import com.zjj.springstu.springHandwriting.framework.annotation.Autowired;
import com.zjj.springstu.springHandwriting.framework.entity.BeanDefinition;
import com.zjj.springstu.springHandwriting.framework.intf.ApplicationContextAware;
import com.zjj.springstu.springHandwriting.framework.intf.BeanNameAware;
import com.zjj.springstu.springHandwriting.framework.intf.BeanPostProcessor;
import com.zjj.springstu.springHandwriting.framework.intf.InitializingBean;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiFunction;

/**
 * 基于注解的Spring容器
 */
public class ZjjAnnotationApplicationContext {
	private Class<?> configClass;

	private static final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

	// 单例池
	private static final Map<String, Object> singlePoolMap = new HashMap<>();

	// 后置处理器
	private static List<BeanPostProcessor> beanPostProcessors = null;

	public ZjjAnnotationApplicationContext(Class<?> configClass) {
		if (!configClass.isAnnotationPresent(ComponentScan.class)) {
			throw new RuntimeException("当前配置类有误，请检查！");
		}

		// 扫描生成 BeanDefinition
		doScan(configClass);

		// 生成懒加载单例bean
		createSingletonBean();
	}

	public Object getBean(String beanName) {
		Object obj = singlePoolMap.get(beanName);
		if (Objects.nonNull(obj)) {
			return obj;
		}
		BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
		if (Objects.isNull(beanDefinition)) {
			throw new RuntimeException("没有该bean的beanDefinition，请检查!");
		}
		return doCreateBean(beanDefinition);
	}

	public Object getBean(Class<?> beanClass) {
		Set<String> beanNameSet = beanDefinitionMap.keySet();
		Iterator<String> iterator = beanNameSet.iterator();
		while (iterator.hasNext()) {
			String beanName = iterator.next();
			if (Objects.equals(beanClass, beanDefinitionMap.get(beanName).getClazz())) {
				return getBean(beanName);
			}
		}
		return null;
	}


	/**
	 * 实例化，主要是针对单例Bean，在Spring容器启动时就生成对象并放到单例池
	 */
	private void createSingletonBean() {
		if (beanDefinitionMap.isEmpty()) {
			return;
		}
		Set<String> beanNameSet = beanDefinitionMap.keySet();
		Iterator<String> iterator = beanNameSet.iterator();
		while (iterator.hasNext()) {
			BeanDefinition beanDefinition = beanDefinitionMap.get(iterator.next());
			// 如果是原型bean 或 懒加载bean 在Spring容器启动时不用初始化
			if (Objects.equals(BeanDefinition.ScopeEnum.PROTOTYPE, beanDefinition.getScope())
					|| beanDefinition.getIsLazy()) {
				continue;
			}

			// 单例bean
			Object beanObj = doCreateBean(beanDefinition);
			if (Objects.nonNull(beanObj)) {
				singlePoolMap.put(beanDefinition.getBeanName(), beanObj);
			}
		}
	}

	/**
	 * 反射调用构造方法创建对象
	 * @param beanDefinition
	 */
	private Object doCreateBean(BeanDefinition beanDefinition) {
		Class<?> beanClass = beanDefinition.getClazz();

		// 实例化bean
		Object beanObj = instantiation(beanDefinition);
		if (Objects.isNull(beanObj)) {
			return null;
		}

		// ioc 依赖注入
		Field[] fields = beanClass.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Autowired.class)) {
				Class<?> fieldType = field.getType();
				Object autowiredBean = getBean(fieldType);
				if (Objects.isNull(autowiredBean)) {
					throw new RuntimeException("依赖注入失败!");
				}
				field.setAccessible(true);
				try {
					field.set(beanObj, autowiredBean);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}

		// 处理Aware回调
		if (beanObj instanceof BeanNameAware) {
			((BeanNameAware) beanObj).setBeanName(beanDefinition.getBeanName());
		}
		if (beanObj instanceof ApplicationContextAware) {
			((ApplicationContextAware) beanObj).setApplicationContext(this);
		}

		// 后置处理器-初始化前回调
		if (!BeanPostProcessor.class.isAssignableFrom(beanClass)) {
			beanPostProcessors = getBeanPostProcessors();
			for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
				beanObj = beanPostProcessor.postProcessBeforeInitialization(beanObj, beanDefinition.getBeanName());
			}
		}

		// 处理初始化钩子
		if (beanObj instanceof InitializingBean) {
			((InitializingBean) beanObj).afterPropertiesSet();
		}

		if (!BeanPostProcessor.class.isAssignableFrom(beanClass)) {
			// 后置处理器-初始化后回调
			for (BeanPostProcessor beanPostProcessor : beanPostProcessors) {
				beanObj = beanPostProcessor.postProcessAfterInitialization(beanObj, beanDefinition.getBeanName());
			}
		}

		return beanObj;
	}

	/**
	 * 获取所有的BeanPostProcessor 处理器
	 * @return
	 */
	private List<BeanPostProcessor> getBeanPostProcessors() {
		List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();
		Set<String> beanNameSet = beanDefinitionMap.keySet();
		Iterator<String> iterator = beanNameSet.iterator();
		while (iterator.hasNext()) {
			String beanName = iterator.next();
			BeanDefinition definition = beanDefinitionMap.get(beanName);
			if (BeanPostProcessor.class.isAssignableFrom(definition.getClazz())) {
				beanPostProcessorList.add((BeanPostProcessor) getBean(beanName));
			}
		}
		return beanPostProcessorList;
	}

	/**
	 * 调用无参构造实例化bean，这里没有实现推断构造方法的流程
	 * @param beanDefinition
	 * @return
	 */
	private Object instantiation(BeanDefinition beanDefinition) {
		Object beanObj = null;

		// 这里直接使用无参构造方法，真实Spring有推断构造方法的流程
		try {
			Constructor<?> constructor = beanDefinition.getClazz().getConstructor();
			if (Objects.isNull(constructor)) {
				throw new RuntimeException("当前bean：" + beanDefinition.getBeanName() + "没有无参构造，无法实例化!");
			}
			beanObj = constructor.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return beanObj;
	}

	/**
	 * 扫描生成 BeanDefinition
	 * @param configClass
	 */
	private void doScan(Class<?> configClass) {
		ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
		String basePackage = componentScan.basePackage();
		if (Objects.isNull(basePackage) || basePackage.length() == 0) {
			return;
		}
		String basePath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
		System.out.println("当前根路径：" + basePath);
		basePackage = basePackage.replace(".", "/");
		String scanClassPath = basePath.concat(basePackage);
		System.out.println("当前扫描类路径为：" + scanClassPath);

		LinkedList<String> beanClassPathList = new LinkedList<>();
		File rootScanDir = new File(scanClassPath);
		findBeanPath(rootScanDir, beanClassPathList);
		System.out.println("当前扫描路径包含的类：" + beanClassPathList);

		/*
			类文件路径转全限定名包路径
			类文件路径形如：..../..../java/main/com/zjj/app/service/UserService.class
		 */
		BiFunction<String, String, String> convertFilePath2ClassPathFun = (filePath, basePackagePath) -> {
			filePath = filePath.replace("\\", ".");
			String classpath = filePath.substring(filePath.indexOf(basePackagePath));
			// 去掉 .class
			classpath = classpath.substring(0, classpath.length() - ".class".length());
			return classpath;
		};
		for (String beanClassPath : beanClassPathList) {

			String classPath = convertFilePath2ClassPathFun.apply(beanClassPath, componentScan.basePackage());
			ClassLoader classLoader = ZjjAnnotationApplicationContext.class.getClassLoader();
			try {
				// 现在还是Spring容器初始化阶段，java中加载类应该是用到时候才会去加载，但是在Spring容器初始化时候又想拿到一些类元信息，
				// 因此Spring引入了ASM字节码技术，我们手写为了方便只能去提前加载类
				Class<?> beanClass = classLoader.loadClass(classPath);
				if (beanClass.isAnnotationPresent(Component.class)) {
					Component component = beanClass.getAnnotation(Component.class);
					String beanName = generateBeanName(beanClass, component);
					BeanDefinition beanDefinition = BeanDefinition.builder()
							.beanName(beanName)
							.clazz(beanClass)
							.scope(component.scope())
							.build();
					beanDefinitionMap.put(beanName, beanDefinition);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 生成beanName默认策略
	 * 这里简单实现一下：把类名首字母换成小写
	 * @param beanClass
	 * @param component
	 * @return
	 */
	private String generateBeanName(Class<?> beanClass, Component component) {
		String beanName = component.beanName();
		if (component.beanName() == null || component.beanName().length() == 0) {
			String simpleNameWithoutFirstChar = beanClass.getSimpleName().substring(1);
			String firstChar = beanClass.getSimpleName().substring(0, 1).toLowerCase();
			beanName = firstChar.concat(simpleNameWithoutFirstChar);
		}

		return beanName;
	}

	/**
	 * 递归遍历所有文件目录
	 * @param rootDir
	 * @param beanClassPathList 可能扫描的路基比较多，使用beanClassPathList有助于提升性能
	 */
	private void findBeanPath(File rootDir, LinkedList<String> beanClassPathList) {
		if (!rootDir.exists()) {
			return;
		}

		File[] files = rootDir.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				findBeanPath(file, beanClassPathList);
			} else {
				beanClassPathList.add(file.getPath());
			}
		}
	}
}
